﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Net;
using System.IO;
using System.Xml.Linq;
using System.Xml;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.Web.Script.Serialization;

namespace ListItTaxi.Web
{   
    public static class Google
    {
        public static Coordinate GeoCode(string address)
        {
            Coordinate result = new Coordinate();

            string url = string.Format(
                "http://maps.google.com/maps/api/geocode/json?address={0}&region=dk&sensor=false", HttpUtility.UrlEncode(address));

            var request = (HttpWebRequest)HttpWebRequest.Create(url);
            request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
            request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(GeoResponse));
            var res = (GeoResponse)serializer.ReadObject(request.GetResponse().GetResponseStream());

            if (res.Results.Length > 0)
            {
                result.Latitude = res.Results[0].Geometry.Location.Lat;
                result.Longitude = res.Results[0].Geometry.Location.Lng;
                return result;
            }
            return null;
        }

        public static string GetLocation(decimal longitude, decimal latitude)
        {
            string url = string.Format(
                "http://maps.googleapis.com/maps/api/geocode/json?latlng={0},{1}&sensor=false", latitude, longitude);

            var request = (HttpWebRequest)HttpWebRequest.Create(url);
            request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
            request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(ReverseGeoResponse));
            var res = (ReverseGeoResponse)serializer.ReadObject(request.GetResponse().GetResponseStream());

            if (res.Results.Length > 0)
            {
                return res.Results[0].FormattedAddress;
            }
            return "";
        }

        public static List<string> FindMatchingAddress(string address)
        {
            List<string> result = new List<string>();

            string url = string.Format(
                "http://maps.google.com/maps/api/geocode/json?address={0}&region=dk&sensor=false", HttpUtility.UrlEncode(address));

            var request = (HttpWebRequest)HttpWebRequest.Create(url);
            request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
            request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(GeoResponse));
            var res = (GeoResponse)serializer.ReadObject(request.GetResponse().GetResponseStream());

            for (int i = 0; i < res.Results.Length; i++)
            {
                result.Add(res.Results[i].ToString());
            }

            return result;
        }
    }

    public class Coordinate
    {
        public double Longitude { get; set; }
        public double Latitude { get; set; }


        public static double toRad(double angle)
        {
            return (Math.PI / 180) * angle;
        }

        public static double toDegrees(double angle)
        {
            return (angle * 180) / Math.PI;
        }

        public static Coordinate InterpolateAzimuth(double latitude, double longitude, double azimuth,
            double distance)
        {
            /*Formula:	lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ))
 	                    lon2 = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))
 	                    θ is the bearing (in radians, clockwise from north);
                        d/R is the angular distance (in radians), where d is the distance (km) travelled and R is the earth’s radius
             */

            Coordinate coord = new Coordinate();
            double dr = distance/6371;
            azimuth = toRad(azimuth); // convert to rad
            double lat1 = toRad(latitude);
            double long1 = toRad(longitude);

            // work out latitude
            coord.Latitude = Math.Asin(Math.Sin(lat1) * Math.Cos(dr) + Math.Cos(lat1) * Math.Sin(dr) *
                Math.Cos(azimuth));

            // work out longitude
            coord.Longitude = long1 + Math.Atan2(Math.Sin(azimuth) * Math.Sin(dr) * Math.Cos(lat1),
                Math.Cos(dr) - Math.Sin(lat1) * Math.Sin(coord.Latitude));

            coord.Longitude = (coord.Longitude + 3 * Math.PI) % (2 * Math.PI) - Math.PI;  // normalise to -180..+180º

            coord.Latitude = toDegrees(coord.Latitude);
            coord.Longitude = toDegrees(coord.Longitude);

            return coord;
        }

        public static double Bearing(double dLongitude, double latitude1, double latitude2)
        {
            /* Formula: θ =	atan2(	sin(Δlong).cos(lat2),
              var dPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
  if (Math.abs(dLon) > Math.PI) dLon = dLon>0 ? -(2*Math.PI-dLon) : (2*Math.PI+dLon);
  var brng = Math.atan2(dLon, dPhi);
  
  return (brng.toDeg()+360) % 360;
             * 
             * 
                        cos(lat1).sin(lat2) − sin(lat1).cos(lat2).cos(Δlong) )
             */


            dLongitude = toRad(dLongitude);
            double lat1 = toRad(latitude1);
            double lat2 = toRad(latitude2);

            double dPhi = Math.Log(Math.Tan(lat2 / 2 + Math.PI / 4) / Math.Tan(lat1 / 2 + Math.PI / 4));

            if (Math.Abs(dLongitude) > Math.PI)
                dLongitude = dLongitude > 0 ? -(2*Math.PI-dLongitude) : (2*Math.PI+dLongitude);

            double bearing = Math.Atan2(dLongitude, dPhi);
            return (toDegrees(bearing) + 360) % 360;
            /*
            double bearing = Math.Atan2(Math.Sin(dLongitude) * Math.Cos(lat2),
                Math.Cos(lat1) * Math.Sin(lat2) - Math.Sin(lat1) * Math.Cos(lat2) *
                Math.Cos(dLongitude));

            return toDegress(bearing);*/
        }
    }

    [DataContract]
    public class GeoResponse
    {
        [DataMember(Name = "status")]
        public string Status { get; set; }
        [DataMember(Name = "results")]
        public CResult[] Results { get; set; }

        [DataContract]
        public class CResult
        {
            [DataMember(Name = "geometry")]
            public CGeometry Geometry { get; set; }

            [DataContract]
            public class CGeometry
            {
                [DataMember(Name = "location")]
                public CLocation Location { get; set; }

                [DataContract]
                public class CLocation
                {
                    [DataMember(Name = "lat")]
                    public double Lat { get; set; }
                    [DataMember(Name = "lng")]
                    public double Lng { get; set; }
                }
            }
        }
    }

    [DataContract]
    public class ReverseGeoResponse
    {
        [DataMember(Name = "status")]
        public string Status { get; set; }
        [DataMember(Name = "results")]
        public CResult[] Results { get; set; }
        
        [DataContract]
        public class CResult
        {
            [DataMember(Name = "formatted_address")]
            public string FormattedAddress { get; set; }
        }
    }
}